﻿using System;
using System.Collections.Generic;
using System.Globalization;
using EOS.Framework.Kernel;
using EOS.Framework.Web.Mvc.Rendering;
using System.Xml;
using Microsoft.Practices.Unity;

namespace EOS.Framework.Web.Mvc
{
    public class Markup : IMarkup
    {
        private List<string> _workingList;
        private Dictionary<string, Control> _controls = new Dictionary<string, Control>();
        private object _token = null;
        private string _xml;
        private MarkupFile _file;
        private Dictionary<string, string> _values = new Dictionary<string, string>();
        private IValueProvider _valueProvider;
        
        public event EventHandler<ControlAttachedEventArgs> ControlAttached;

        private bool _debugMode;

        public bool DebugMode
        {
            get { return _debugMode; }
            set { _debugMode = value; }
        }

        private Container _container;

        public Container Container
        {
            get { return _container; }
            set { _container = value; }
        }
  
        private Markup _child;

        public Markup Child
        {
            get { return _child; }
            set { _child = value; }
        }

        #region Constructors

        public Markup(XmlDocument xmlDocument)
            : this(xmlDocument.InnerXml, new MarkupFile(xmlDocument), null, null)
        { }

        private Markup(string markup, MarkupFile file, object token, Container container)
        {
            if (_debugMode)
            {
                _workingList = MarkupHelper.FindTags(markup);
            }
            else
            {
                _workingList = new List<string>();
            }

            try
            {
                _valueProvider = Unity.Current.Resolve<IValueProvider>();
            }
            catch (ResolutionFailedException)
            {
            }

            this._xml = markup;
            this._file = file;
            this._token = token;
            this._container = container;
        }        
        
        #endregion


        private void OnRenderOuter(IXhtmlWriter writer, PageContext context, string name)
        {
            IContainerFactory factory = Unity.Current.Resolve<IContainerFactory>();

            IWebPart webPart = factory.CreateWebPart(name);
            if (webPart != null)
            {
                _container.Parent = webPart;

                WebPart wp = webPart as WebPart;
                if (wp != null)
                {
                    wp.Markup.Child = this;
                }
                webPart.Execute(writer, context);
            }
        }

        private void OnRenderInner(IXhtmlWriter writer, PageContext context, string name)
        {
            IContainerFactory factory = Unity.Current.Resolve<IContainerFactory>();

            IWebPart webPart = factory.CreateWebPart(name);
            if (webPart != null)
            {
                webPart.Parent = _container;

                webPart.Execute(writer, context);
            }

            AddEventHandler();
        }

        private void OnRenderRegion(IXhtmlWriter writer, PageContext context, string name)
        {
            if (_child != null)
            {
                _child._file.ReplaceRegion(writer, context, name);
            }
        }

        private bool OnRenderControl(MarkupFileControlFoundParameters parameters)
        {
            if ((_debugMode == false && _workingList.Contains(parameters.Name) == true) ||
                (_debugMode == true && _workingList.Contains(parameters.Name) == false))
            {
                return true;
            }

            bool handled = false;

            Control control = null;
            if (_controls.TryGetValue(parameters.Name, out control))
            {
                control.TagName = parameters.Tag;
                foreach (string key in parameters.Parameters.Keys)
                {
                    control.AddAttribute(key, parameters.Parameters[key]);
                }

                MarkupControl markupControl = control as MarkupControl;
                if (markupControl != null)
                {
                    markupControl.SetMarkup(new Markup(parameters.InnerXml, _file, parameters.Token, _container));
                }

                control.Render(parameters.Writer, parameters.Context);
                handled = true;
            }

            AddEventHandler();

            return handled;
        }

        private string OnValueFound(string value)
        {
            string result = null;
            _values.TryGetValue(value, out result);

            if (result == null && _valueProvider != null)
            {
                result = _valueProvider.GetValue(value);
            }

            return result;
        }

        public void RenderMarkup(IXhtmlWriter writer, PageContext context)
        {
            AddEventHandler();

            _file.RenderMarkup(writer, context, _token);
        }

        private void AddEventHandler()
        {
            _file.RenderControl = new MarkupFileControlFoundHandler(OnRenderControl);

            _file.RenderRegion = new MarkupFileRenderHandler(OnRenderRegion); 
            _file.RenderInner = new MarkupFileRenderHandler(OnRenderInner);
            _file.RenderOuter = new MarkupFileRenderHandler(OnRenderOuter);

            _file.ValueFound = new MarkupFileValueFoundHandler(OnValueFound);
        }

        private void RemoveEventHandler()
        {
            _file.RenderRegion = null;
            _file.RenderControl = null;
            _file.RenderInner = null;
            _file.RenderOuter = null;

            _file.ValueFound = null;
        }


        #region Control Management

        public void Hide(string key)
        {
            Hide(key, true);
        }

        public void Hide(string key, bool throwExceptions)
        {
            if (_debugMode)
            {
                if (_workingList.Contains(key) == true)
                {
                    _workingList.Remove(key);
                }
                else
                {
                    if (throwExceptions)
                    {
                        throw new ArgumentException(
                            string.Format(CultureInfo.CurrentCulture, Properties.Resources.TagNotFound, key));
                    }
                }
            }
            else
            {
                _workingList.Add(key);
            }
        }

        public void SetValue(string key, string value)
        {
            _values[key] = value;
        }

        public void SetValue(string key, string value, params object[] args)
        {
            _values[key] = string.Format(value, args);
        }

        public void Attach(string key, Control control)
        {
            Attach(key, control, true);
        }

        public void Attach(string key, Control control, bool throwExceptions)
        {
            if (_debugMode)
            {
                if (_workingList.Contains(key) == false)
                {
                    if (throwExceptions)
                    {
                        throw new ArgumentException(
                            string.Format(CultureInfo.CurrentCulture, Properties.Resources.TagNotFound, key));
                    }
                    else
                    {
                        return;
                    }
                }
            } 
            
            _controls[key] = control;

            // Send a message to the control to show, that the control was attached.
            control.NotifyAttached(this, key);

            OnControlAttached(new ControlAttachedEventArgs(control, this));
        }

        private void OnControlAttached(ControlAttachedEventArgs e) 
        {
            if (ControlAttached != null)
            {
                ControlAttached(this, e);
            }
        }

        #endregion

        #region ICloneable Member

        public object Clone()
        {
            Markup markup = new Markup(_xml, _file, _token, _container);
            return markup;
        }

        #endregion
    }
}
